/*!
 * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
 * Licensed under the MIT License.
 */

import { strict as assert } from "node:assert";
import type { IFluidCompatibilityMetadata, Logger } from "@fluidframework/build-tools";
import { formatISO } from "date-fns";
import { describe, it } from "mocha";

import UpdateGenerationCommand, {
	daysInMonthApproximation,
	generateLayerFileContent,
	isCurrentPackageVersionPatch,
	maybeGetNewGeneration,
} from "../../../commands/generate/compatLayerGeneration.js";

describe("generate:compatLayerGeneration", () => {
	const minimumCompatWindowMonths = UpdateGenerationCommand.flags.minimumCompatWindowMonths
		.default as number;

	// Mock logger that captures log calls for verification
	const createMockLogger = (): Logger => {
		return {
			log: (): void => {},
			info: (): void => {},
			warning: (): void => {},
			errorLog: (): void => {},
			verbose: (): void => {},
		};
	};

	it("should correctly detect patch versions", () => {
		assert.strictEqual(isCurrentPackageVersionPatch("1.2.3"), true);
		assert.strictEqual(isCurrentPackageVersionPatch("1.2.0"), false);
		assert.strictEqual(isCurrentPackageVersionPatch("3.0.0"), false);
	});

	it("should generate correctly formatted output", () => {
		const result = generateLayerFileContent(42);

		// Check that it includes the copyright header
		assert(result.includes("Copyright (c) Microsoft Corporation"));
		assert(result.includes("THIS IS AN AUTOGENERATED FILE"));

		// Check the exports
		assert(result.includes("export const generation = 42;"));

		// Ensure proper formatting
		assert(/export const generation = \d+;/.test(result));
	});

	it("should not change generation when package version has not changed", () => {
		const mockLogger = createMockLogger();
		const currentVersion = "2.0.0";
		const mockMetadata: IFluidCompatibilityMetadata = {
			generation: 5,
			releaseDate: "2025-01-01",
			releasePkgVersion: currentVersion,
		};

		const result = maybeGetNewGeneration(
			currentVersion,
			mockMetadata,
			minimumCompatWindowMonths,
			mockLogger,
		);

		assert.strictEqual(result, undefined);
	});

	it("should not change generation when only patch version has changed", () => {
		const mockLogger = createMockLogger();
		const previousGeneration = 5;
		const previousVersion = "2.0.0";
		const currentVersion = "2.0.1"; // Only patch change

		// Create a date 2 months ago (should normally trigger increment)
		const oldDate = new Date();
		oldDate.setDate(oldDate.getDate() - 2 * daysInMonthApproximation);
		const oldDateString = formatISO(oldDate, { representation: "date" });
		const mockMetadata: IFluidCompatibilityMetadata = {
			generation: previousGeneration,
			releaseDate: oldDateString,
			releasePkgVersion: previousVersion,
		};

		const result = maybeGetNewGeneration(
			currentVersion,
			mockMetadata,
			minimumCompatWindowMonths,
			mockLogger,
		);

		assert.strictEqual(result, undefined);
	});

	it("should update generation when time since last release is 1+ months with minor version change", () => {
		const mockLogger = createMockLogger();
		const previousGeneration = 5;
		const monthsSincePreviousRelease = 1; // More than 1 month

		// Create a date monthsSincePreviousRelease months ago
		const oldDate = new Date();
		oldDate.setDate(oldDate.getDate() - monthsSincePreviousRelease * daysInMonthApproximation);
		const oldDateString = formatISO(oldDate, { representation: "date" });
		const mockMetadata: IFluidCompatibilityMetadata = {
			generation: previousGeneration,
			releaseDate: oldDateString,
			releasePkgVersion: "1.0.0",
		};
		const result = maybeGetNewGeneration(
			"1.1.0", // Minor version change
			mockMetadata,
			minimumCompatWindowMonths,
			mockLogger,
		);

		assert.strictEqual(result, previousGeneration + monthsSincePreviousRelease);
	});

	it("should update generation when time since last release is 1+ months with major version change", () => {
		const mockLogger = createMockLogger();
		const previousGeneration = 5;
		const monthsSincePreviousRelease = 2; // 2 months

		// Create a date monthsSincePreviousRelease months ago
		const oldDate = new Date();
		oldDate.setDate(oldDate.getDate() - monthsSincePreviousRelease * daysInMonthApproximation);
		const oldDateString = formatISO(oldDate, { representation: "date" });
		const mockMetadata: IFluidCompatibilityMetadata = {
			generation: previousGeneration,
			releaseDate: oldDateString,
			releasePkgVersion: "1.0.0",
		};
		const result = maybeGetNewGeneration(
			"2.0.0", // Major version change
			mockMetadata,
			minimumCompatWindowMonths,
			mockLogger,
		);

		assert.strictEqual(result, previousGeneration + monthsSincePreviousRelease);
	});

	it("should not update generation when time since last release is < 1 month", () => {
		const mockLogger = createMockLogger();
		const previousGeneration = 5;
		const daysSincePreviousRelease = 31; // Less than approx. 1 month

		// Create a date daysSincePreviousRelease days ago
		const oldDate = new Date();
		oldDate.setDate(oldDate.getDate() - daysSincePreviousRelease);
		const oldDateString = formatISO(oldDate, { representation: "date" });
		const mockMetadata: IFluidCompatibilityMetadata = {
			generation: previousGeneration,
			releaseDate: oldDateString,
			releasePkgVersion: "1.0.0",
		};
		const result = maybeGetNewGeneration(
			"1.1.0", // Minor version change but not enough time elapsed
			mockMetadata,
			minimumCompatWindowMonths,
			mockLogger,
		);

		assert.strictEqual(result, undefined);
	});

	it("should cap generation increment to minimumCompatWindowMonths - 1", () => {
		const mockLogger = createMockLogger();
		const previousGeneration = 5;

		// Create a date 12 months ago (way beyond threshold)
		const veryOldDate = new Date();
		veryOldDate.setFullYear(veryOldDate.getFullYear() - 1);
		const oldDateString = formatISO(veryOldDate, { representation: "date" });
		const mockMetadata: IFluidCompatibilityMetadata = {
			generation: previousGeneration,
			releaseDate: oldDateString,
			releasePkgVersion: "1.0.0",
		};
		const result = maybeGetNewGeneration(
			"2.0.0", // Major version change
			mockMetadata,
			minimumCompatWindowMonths,
			mockLogger,
		);

		assert.strictEqual(result, previousGeneration + minimumCompatWindowMonths - 1);
	});

	it("should throw error for invalid date format", () => {
		const mockLogger = createMockLogger();
		// Test with invalid date format
		const invalidMetadata: IFluidCompatibilityMetadata = {
			generation: 5,
			releaseDate: "invalid-date", // Invalid date format
			releasePkgVersion: "1.0.0",
		};

		assert.throws(() => {
			maybeGetNewGeneration("2.0.0", invalidMetadata, minimumCompatWindowMonths, mockLogger);
		}, /not a valid date/);
	});

	it("should throw error for invalid package version format", () => {
		const mockLogger = createMockLogger();
		// Test with invalid package version format
		const invalidMetadata: IFluidCompatibilityMetadata = {
			generation: 5,
			releaseDate: "2025-11-03",
			releasePkgVersion: "invalid-version", // Invalid package version
		};

		assert.throws(() => {
			maybeGetNewGeneration("2.0.0", invalidMetadata, minimumCompatWindowMonths, mockLogger);
		}, /Invalid Version/);
	});

	it("should fail when current date is older than previous release date", () => {
		const mockLogger = createMockLogger();

		// Test with a future date
		const futureDate = new Date();
		futureDate.setMonth(futureDate.getMonth() + 2);
		const futureDateString = formatISO(futureDate, { representation: "date" });
		const mockMetadata: IFluidCompatibilityMetadata = {
			generation: 5,
			releaseDate: futureDateString,
			releasePkgVersion: "1.0.0",
		};

		assert.throws(
			() =>
				maybeGetNewGeneration(
					"2.0.0", // Major version change
					mockMetadata,
					minimumCompatWindowMonths,
					mockLogger,
				),
			/Current date is older that previous release date/,
		);
	});
});
